"""
Dynamic MCP tool generator from Swagger API specifications
"""
import inspect
import types
from typing import Dict, List, Any, Optional, Callable
from pydantic import Field, BaseModel
import requests
import logging
from urllib.parse import urljoin

from ..config import InCloudConfig
from .client import SwaggerClient

logger = logging.getLogger(__name__)


class APIExecutor:
    """Executes API calls based on Swagger specifications"""
    
    def __init__(self, config: InCloudConfig):
        self.config = config
        self.base_url = config.swagger.base_url
        self.session = requests.Session()
        
        # Setup authentication
        if config.auth:
            self._setup_auth()
    
    def _setup_auth(self):
        """Setup authentication for API calls"""
        auth_config = self.config.auth
        if auth_config.type == "bearer" and auth_config.token:
            self.session.headers.update({
                "Authorization": f"Bearer {auth_config.token}"
            })
        elif auth_config.type == "basic" and auth_config.username and auth_config.password:
            self.session.auth = (auth_config.username, auth_config.password)
        elif auth_config.type == "api_key" and auth_config.api_key:
            header_name = auth_config.api_key_header or "X-API-Key"
            self.session.headers.update({
                header_name: auth_config.api_key
            })
    
    def execute_api(self, service_name: str, method: str, path: str, parameters: Dict[str, Any]) -> Any:
        """
        Execute an API call
        
        Args:
            service_name: Name of the service
            method: HTTP method
            path: API path
            parameters: API parameters
            
        Returns:
            API response
        """
        try:
            # Build URL - Based on the logs, the correct pattern is /{service_name}{path}
            # For example: /icompute/v1/vms, not /api/icompute/v1/vms
            url = urljoin(self.base_url, f"/{service_name}" + path)
            logger.info(f"Built URL: {url}")
            
            # Separate parameters by type
            query_params = {}
            path_params = {}
            body_data = {}
            headers = {"incloud_innercall":"true"}

            # List of parameters that should be treated as headers
            header_params = {'Authorization', 'Content-Type', 'Accept', 'User-Agent', 'X-API-Key'}

            for key, value in parameters.items():
                if value is None:
                    continue

                # Check if this parameter should be a header
                if key in header_params or key.lower().startswith('x-'):
                    headers[key] = value
                # Simple heuristic for parameter placement
                elif '{' + key + '}' in path:
                    path_params[key] = value
                elif method.upper() in ['POST', 'PUT', 'PATCH']:
                    body_data[key] = value
                else:
                    query_params[key] = value
            
            # Replace path parameters
            final_url = url
            for key, value in path_params.items():
                final_url = final_url.replace('{' + key + '}', str(value))
            
            # Make the request
            request_kwargs = {
                'timeout': self.config.swagger.timeout,
                'params': query_params if query_params else None,
            }

            # Merge custom headers with session headers
            if headers:
                # Create a copy of session headers and update with custom headers
                merged_headers = self.session.headers.copy()
                merged_headers.update(headers)
                request_kwargs['headers'] = merged_headers
            
            if body_data and method.upper() in ['POST', 'PUT', 'PATCH']:
                request_kwargs['json'] = body_data
            
            logger.info(f"Executing API: {method} {final_url}")
            logger.info(f"Request query params: {query_params}")
            logger.info(f"Request path params: {path_params}")
            logger.info(f"Request body data: {body_data}")

            # Log headers (mask sensitive information)
            headers_to_log = {}
            if 'headers' in request_kwargs and request_kwargs['headers']:
                for k, v in request_kwargs['headers'].items():
                    if k.lower() == 'authorization':
                        # Mask the token for security, but show it exists
                        if v.startswith('Bearer '):
                            headers_to_log[k] = f"Bearer {v[7:15]}...{v[-8:]}"
                        else:
                            headers_to_log[k] = f"{v[:8]}...{v[-4:]}"
                    else:
                        headers_to_log[k] = v
            logger.info(f"Request headers: {headers_to_log}")
            logger.debug(f"Full request kwargs: {request_kwargs}")

            response = self.session.request(method, final_url, **request_kwargs)

            # Log response details
            logger.info(f"Response status code: {response.status_code}")
            logger.info(f"Response headers: {dict(response.headers)}")

            # Log response body (truncate if too long)
            response_text = response.text
            if len(response_text) > 1000:
                logger.info(f"Response body (truncated): {response_text[:500]}...{response_text[-500:]}")
            else:
                logger.info(f"Response body: {response_text}")

            response.raise_for_status()

            # Try to return JSON, fallback to text
            try:
                return response.json()
            except:
                return response.text
                
        except Exception as e:
            logger.error(f"API execution failed: {method} {final_url if 'final_url' in locals() else path}")
            logger.error(f"Error details: {str(e)}")
            logger.error(f"Error type: {type(e).__name__}")

            # If it's an HTTP error, log more details
            if hasattr(e, 'response'):
                logger.error(f"HTTP error status: {e.response.status_code}")
                logger.error(f"HTTP error headers: {dict(e.response.headers)}")
                logger.error(f"HTTP error body: {e.response.text}")

            raise


class ToolGenerator:
    """Generates MCP tools from Swagger API specifications"""
    
    def __init__(self, config: InCloudConfig):
        self.config = config
        self.swagger_client = SwaggerClient(config)
        self.api_executor = APIExecutor(config)
        self.type_mapping = {
            'string': str,
            'integer': int,
            'number': float,
            'boolean': bool,
            'array': list,
            'object': dict
        }
    
    def _convert_swagger_type(self, swagger_type: str, swagger_format: Optional[str] = None) -> type:
        """Convert Swagger type to Python type"""
        if swagger_format == 'int64':
            return int
        elif swagger_format == 'float':
            return float
        elif swagger_format == 'double':
            return float
        else:
            return self.type_mapping.get(swagger_type, str)
    
    def _create_parameter_field(self, param: Dict[str, Any]) -> Any:
        """Create a Pydantic Field for a parameter"""
        param_type = param.get('type', 'string')
        param_format = param.get('format')
        description = param.get('description', '')
        required = param.get('required', False)
        
        # Handle schema-based parameters
        schema = param.get('schema', {})
        if schema:
            param_type = schema.get('type', param_type)
            param_format = schema.get('format', param_format)
        
        python_type = self._convert_swagger_type(param_type, param_format)
        
        if required:
            return Field(description=description)
        else:
            return Field(default=None, description=description)
    
    def generate_tool_function(self, service_name: str, api_details: Dict[str, Any]) -> Callable:
        """
        Generate a tool function for a specific API
        
        Args:
            service_name: Name of the service
            api_details: API details from Swagger
            
        Returns:
            Generated tool function
        """
        method = api_details['method']
        path = api_details['path']
        summary = api_details.get('summary', '')
        description = api_details.get('description', summary)
        
        # Extract parameters
        parameters = self.swagger_client.extract_parameters(api_details)
        
        # Create function signature
        func_params = []
        annotations = {}
        defaults = {}

        # Separate required and optional parameters to ensure correct order
        required_params = []
        optional_params = []

        logger.debug(f"Processing {len(parameters)} parameters for {service_name} {method} {path}")

        for param in parameters:
            param_name = param['name']
            param_type = param.get('type', 'string')
            param_format = param.get('format')
            param_required = param.get('required', False)
            param_description = param.get('description', '')

            # Handle schema-based parameters
            schema = param.get('schema', {})
            if schema:
                param_type = schema.get('type', param_type)
                param_format = schema.get('format', param_format)

            python_type = self._convert_swagger_type(param_type, param_format)

            param_info = {
                'name': param_name,
                'type': python_type,
                'required': param_required,
                'description': param_description
            }

            logger.debug(f"  Parameter: {param_name} (type: {param_type}, required: {param_required}, desc: '{param_description}')")

            if param_required:
                required_params.append(param_info)
            else:
                optional_params.append(param_info)

        logger.debug(f"Required parameters: {[p['name'] for p in required_params]}")
        logger.debug(f"Optional parameters: {[p['name'] for p in optional_params]}")

        # Add required parameters first
        for param_info in required_params:
            # For required parameters, use Field without default value
            field = Field(description=param_info['description'])
            logger.debug(f"  Creating required parameter: {param_info['name']} with description: '{param_info['description']}'")
            func_params.append(inspect.Parameter(
                param_info['name'],
                inspect.Parameter.POSITIONAL_OR_KEYWORD,
                default=field,
                annotation=param_info['type']
            ))
            annotations[param_info['name']] = param_info['type']

        # Add optional parameters after required ones
        for param_info in optional_params:
            field = Field(default=None, description=param_info['description'])
            defaults[param_info['name']] = None
            func_params.append(inspect.Parameter(
                param_info['name'],
                inspect.Parameter.POSITIONAL_OR_KEYWORD,
                default=field,
                annotation=param_info['type']
            ))
            annotations[param_info['name']] = param_info['type']
        
        # Create function
        def api_function(*args, **kwargs):
            # Bind arguments
            sig = inspect.Signature(func_params)
            bound_args = sig.bind(*args, **kwargs)
            bound_args.apply_defaults()

            # Log the tool call
            logger.info(f"Dynamic tool called: {service_name.upper()}_{method}_{path.replace('/', '_').replace('{', '').replace('}', '')}")
            logger.info(f"Tool arguments: {bound_args.arguments}")

            # Execute API call
            return self.api_executor.execute_api(
                service_name=service_name,
                method=method,
                path=path,
                parameters=bound_args.arguments
            )
        
        # Set function metadata
        function_name = f"{service_name.upper()}_{method}_{path.replace('/', '_').replace('{', '').replace('}', '')}"
        function_name = function_name.replace('-', '_').replace('.', '_')

        # 增强描述信息，添加术语说明
        try:
            from ..terminology import enhance_tool_description
            enhanced_description = enhance_tool_description(description, service_name, path)
        except ImportError:
            enhanced_description = description

        api_function.__name__ = function_name
        api_function.__doc__ = f"{enhanced_description}\n\nService: {service_name}\nMethod: {method}\nPath: {path}"
        api_function.__annotations__ = annotations
        api_function.__signature__ = inspect.Signature(func_params)
        
        return api_function
    
    def generate_service_tools(self, service_name: str) -> List[Callable]:
        """
        Generate all tools for a specific service
        
        Args:
            service_name: Name of the service
            
        Returns:
            List of generated tool functions
        """
        tools = []
        
        try:
            # Get configured APIs for this service
            configured_apis = self.config.get_service_apis(service_name)

            logger.info(f"Found {len(configured_apis)} configured APIs for service {service_name}")
            for i, api in enumerate(configured_apis):
                logger.info(f"  Config API {i+1}: {api.method} {api.path} (enabled: {api.enabled})")

            if not configured_apis:
                logger.warning(f"No configured APIs found for service {service_name}")
                return tools

            # Get all available APIs from Swagger
            available_apis = self.swagger_client.get_service_apis(service_name)

            logger.info(f"Found {len(available_apis)} available APIs from Swagger for service {service_name}")
            for i, api in enumerate(available_apis):
                logger.info(f"  Swagger API {i+1}: {api.get('method', 'N/A')} {api.get('path', 'N/A')}")

            # Generate tools for configured APIs
            for config_api in configured_apis:
                logger.info(f"Processing config API: {config_api.method} {config_api.path}")

                # Find matching API in Swagger documentation
                matching_api = None
                for swagger_api in available_apis:
                    if (swagger_api['method'] == config_api.method.upper() and
                        swagger_api['path'] == config_api.path):
                        matching_api = swagger_api
                        logger.info(f"  ✅ Found matching Swagger API for {config_api.method} {config_api.path}")
                        break

                if matching_api:
                    try:
                        tool_func = self.generate_tool_function(service_name, matching_api)
                        tools.append(tool_func)
                        logger.info(f"  🔧 Generated tool: {tool_func.__name__}")
                    except Exception as e:
                        logger.error(f"  ❌ Failed to generate tool for {config_api.method} {config_api.path}: {e}")
                else:
                    logger.warning(f"  ⚠️  API not found in Swagger docs: {config_api.method} {config_api.path}")
                    logger.warning(f"     Available methods/paths in Swagger:")
                    for swagger_api in available_apis[:5]:  # Show first 5 for debugging
                        logger.warning(f"       {swagger_api.get('method', 'N/A')} {swagger_api.get('path', 'N/A')}")

            logger.info(f"Successfully generated {len(tools)} tools for service {service_name}")
            return tools
            
        except Exception as e:
            logger.error(f"Failed to generate tools for service {service_name}: {e}")
            return []
